package balanceCalculator;

import java.util.ArrayList;
import java.util.List;

import main.MainTest;
import service.BalanceService;
import service.OperationService;
import domain.Balance;
import domain.Operation;
import exception.InvalidBusinessStateException;

public class Calculator {
	
	public static List<Operation> calculate() {
		List<Operation> operations = new ArrayList<Operation>();
		
		List<Balance> balances = BalanceService.getInstance().getAll();
		balances = cleanBalances(balances);
		
		while(!done(balances)) {
			boolean keepGoing = true;
			for (Balance currentBalance : balances) {
				if (!keepGoing) break;
				// if balance 0.0, user is already out of the game
				if (currentBalance.getBalance() == 0.0) continue;
				for (Balance otherBalance : balances) {
					// no use comparing same balances
					if (currentBalance.equals(otherBalance)) continue;
					// if balance is 0.0, user is already out of the game
					if (otherBalance.getBalance() == 0.0) continue;
					// if we have a pair match, create operation
					if ((currentBalance.getBalance() + otherBalance.getBalance()) == 0.0) {
						Operation operation = new Operation();
						// find who's owing who
						if (currentBalance.getBalance() > 0) {
							operation.setAmount(currentBalance.getBalance());
							operation.setRoommateGiver(currentBalance.getRoommate());
							operation.setRoommateReceiver(otherBalance.getRoommate());
							operations.add(operation);
							// debug purpose
							MainTest.displayData("DEBUG PAIR");
							
							//reset balances
							currentBalance.setBalance(0.0);
							otherBalance.setBalance(0.0);
							keepGoing = false;
							break;
						}
					}
				}
			}
			balances = cleanBalances(balances);
			if (!done(balances)) {
				keepGoing = true;
				for(Balance currentBalance : balances) {
					if (!keepGoing) break;
					// if balance out of the game, skip
					if (currentBalance.getBalance() == 0.0) continue;
					for (Balance otherBalance : balances) {
						// if balance out of the game, skip
						if (otherBalance.getBalance() == 0.0) continue;
						// no use comparing same balance, skip
						if (otherBalance.equals(currentBalance)) continue;
						// if we can work with these two go on, else skip
						if (notSameSign(currentBalance.getBalance(), otherBalance.getBalance())) {
							Balance owedBalance = null;
							Balance owingBalance = null;
							// find out who's owed and who's owing
							if (currentBalance.getBalance() < 0.0) {
								owedBalance = currentBalance;
								owingBalance = otherBalance;
							} else {
								owedBalance = otherBalance;
								owingBalance = currentBalance;
							}
							Operation operation = new Operation();
							operation.setRoommateGiver(owingBalance.getRoommate());
							operation.setRoommateReceiver(owedBalance.getRoommate());
							// find out operation amount by figuring how much owingBalance can/need to give to owedBalance
							if (owedBalance.getBalance()+owingBalance.getBalance() > 0) {
								double operationAmount = owedBalance.getBalance()*-1.0;
								operation.setAmount(operationAmount);
								owedBalance.setBalance(0.0);
								owingBalance.setBalance(owingBalance.getBalance()-operationAmount);
							} else {
								double operationAmount = owingBalance.getBalance();
								operation.setAmount(operationAmount);
								owedBalance.setBalance(owedBalance.getBalance()+operationAmount);
								owingBalance.setBalance(0.0);
							}
							// register operation
							operations.add(operation);
							// debug purpose
							MainTest.displayData("DEBUG NO PAIR");
							// my job here is done
							keepGoing = false;
							break;
						}
					}
				}
			}
			balances = cleanBalances(balances);
		}
		// persist in DB
		for (Operation operation : operations) {
			try {
				operation = OperationService.getInstance().store(operation);
			} catch (InvalidBusinessStateException e) {
				throw new RuntimeException("IMPOSSIBRU");
			}
		}
		// update balances in DB
		for (Balance balance : BalanceService.getInstance().getAll()) {
			try {
				BalanceService.getInstance().update(balance);
			} catch (InvalidBusinessStateException e) {
				throw new RuntimeException("IMPOSSIBRU");
			}
		}
		return operations;
	}

	/**
	 * Removes out of the game balances (where balance = 0.0)
	 * @param balances to clean
	 * @return list of Balance still in the game
	 */
	private static List<Balance> cleanBalances(List<Balance> balances) {
		List<Balance> balancesReturn = new ArrayList<Balance>();
		for (Balance balance : balances) {
			if (balance.getBalance() != 0.0) {
				balancesReturn.add(balance);
			}
		}
		return balancesReturn;
	}

	/**
	 * finds out if the two balances can work together (one owes and the other is owed)
	 * @param balance
	 * @param balance2
	 * @return can work together or not
	 */
	private static boolean notSameSign(double balance, double balance2) {
		if (balance > 0.0 && balance2 > 0.0) return false;
		if (balance < 0.0 && balance2 < 0.0) return false;
		return true;
	}

	/**
	 * finds out if there's still anyone in the game
	 * @param balances that still might be in the game
	 * @return done or not
	 */
	private static boolean done(List<Balance> balances) {
		for (Balance balance : balances) {
			if (balance.getBalance() != 0.0)
				return false;
		}
		return true;
	}
}
